from __future__ import unicode_literals
from __future__ import print_function

import os
import re
import sys
import json
import time
import zlib
import codecs
import hashlib
import datetime
import tornado.web

try:
    FileNotFoundError
except NameError:
    FileNotFoundError = IOError

class BaseHandler(tornado.web.RequestHandler):
    @property
    def store(self):
        return self.application.store

    @property
    def cached(self):
        return self.application.cached

    def CreateAppDir(self, app):
        app_dir = self.store + '/' + app
        try:
            os.stat(app_dir)
        except (OSError, FileNotFoundError):
            os.mkdir(app_dir)
            with codecs.open(app_dir + '/alias.json', mode='a', encoding='utf-8') as fp:
                fp.write('{}')
        return app_dir

    def FindAlias(self, ver, name, value):
        find = []
        if ver in value:
            idx = value.index(ver)
            while idx >= 0:
                find.append(name[idx])
                idx = value.index(ver, idx + 1) if ver in value[idx + 1 :] else -1
        return find

    def GetAliasVersion(self, app, alias):
        app_dir = self.store + '/' + app
        try:
            with codecs.open(app_dir + '/alias.json', mode='r', encoding='utf-8') as fp:
                alias_json = json.load(fp)
                if alias in alias_json:
                    return alias_json[alias]
                else:
                    return None
        except (OSError, FileNotFoundError):
            return None

    def FindVersion(self, app, ver):
        app_dir = self.store + '/' + app
        if re.match(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}', ver):
            ver += '.json' if not ver.endswith('.json') else ''
        else:
            ver = self.GetAliasVersion(app_dir, ver)
        return ver

class LatestVersion(BaseHandler):
    def get(self, app):
        app_dir = self.CreateAppDir(app)
        latest = self.GetAliasVersion(app, 'latest-version')
        minimum_required = self.GetAliasVersion(app, 'minimum-required')
        content = None
        try:
           with codecs.open(app_dir + '/' + latest, mode='r', encoding='utf-8') as fp:
               latest_json = json.load(fp)
               latest_json['minimum-required'] = minimum_required
               content = json.dumps(latest_json)
               self.add_header('Content-Encoding', 'deflate')
               self.write(zlib.compress(content))
        except FileNotFoundError:
            self.send_error(500, **{'exc_info': sys.exc_info()})

    def post(self, app):
        app_dir = self.CreateAppDir(app)
        body = json.loads(self.request.body)

        try:
            with codecs.open(app_dir + '/' + 'alias.json', mode='r+', encoding='utf-8') as fp:
                alias = json.load(fp)
                latest = self.FindVersion(app_dir, body['latest-version'])
                if latest:
                    alias['latest-version'] = latest
                    fp.seek(0)
                    json.dump(alias, fp)
                else:
                    self.send_error(400, **{'exc_info': 'illegal version'})
        except (OSError, FileNotFoundError):
            self.send_error(500, **{'exc_info': sys.exc_info()})

class Version(BaseHandler):
    def get(self, app):
        app_dir = self.CreateAppDir(app)
        version_file = self.get_argument('meta', '')
        if version_file:
            version_file = self.FindVersion(app, version_file)

            try:
                with codecs.open(app_dir + '/' + version_file, mode='r', encoding='utf-8') as fp:
                    self.write(zlib.compress(fp.read()))
                self.finish()
            except (OSError, FileNotFoundError):
                self.send_error(404, **{'exc_info': sys.exc_info()})
        else:
            versions = os.listdir(app_dir)
            if 'alias.json' in versions:
                versions.remove('alias.json')

            versions.sort()
            alias = None
            reply = {}
            reply['latest-version'] = self.GetAliasVersion(app, 'latest-version')
            reply['minimum-required'] = self.GetAliasVersion(app, 'minimum-required')
            try:
                with codecs.open(app_dir + '/alias.json', mode='r', encoding='utf-8') as fp:
                    alias = json.load(fp)
            except (OSError, FileNotFoundError):
                pass
            name = alias.keys()
            value = alias.values()
            alias = []
            for x in versions:
                tags = self.FindAlias(x, name, value)
                if tags:
                    alias.append(tags)
                else:
                    alias.append(None)
            reply['alias'] = alias

            reply['other'] = versions
            self.add_header('Content-Encoding', 'deflate')
            self.write(zlib.compress(json.dumps(reply)))

    def post(self, app):
        app_dir = self.CreateAppDir(app)
        body = json.loads(self.request.body)
        files = body['files']
        valid = True
        if files:
            for x in files:
                if 'path' not in x or 'size' not in x or 'digest' not in x:
                    valid = False
                    break
        else:
            valid = False

        if valid:
            ver = datetime.datetime.now()
            verstr = ver.isoformat()
            body['version'] = verstr
            fname = verstr + '.json'
            with codecs.open(app_dir + '/' + fname, mode='w', encoding='utf-8') as fp:
                json.dump(body, fp)
            reply = {}
            reply['version'] = verstr + '.json'
            self.write(json.dumps(reply))
        else:
            self.send_error(400, **{'exc_info': 'illegal version file'})

class Cached(BaseHandler):

    def get(self, fname):
        try:
            content = None
            path = self.cached + '/' + fname
            with codecs.open(path, mode='rb') as fp:
                content = zlib.compress(fp.read())
                self.add_header('Content-Encoding', 'deflate')
                self.write(content)
        except (OSError, FileNotFoundError):
            self.send_error(404, **{'exc_info': sys.exc_info()})

    def post(self, fname):
        try:
            path = self.cached + '/' + fname
            os.stat(path)
        except (OSError, FileNotFoundError):
            SHA1 = hashlib.sha1()
            SHA1.update(self.request.body)
            if SHA1.hexdigest() == fname:
                with codecs.open(path, mode='wb') as fp:
                    fp.write(self.request.body)
            else:
                print("sha1 don't match,", SHA1.hexdigest(), fname)
                self.send_error(400)

    def head(self, fname):
        try:
            path = self.cached + '/' + fname
            stat = os.stat(path)
            mtime = time.localtime(stat.st_mtime)
            self.add_header('Last-Modified', time.asctime(mtime))
            self.add_header('Content-Length', stat.st_size)
            self.finish()
        except (OSError, FileNotFoundError):
            self.send_error(404)

class MinimumRequired(BaseHandler):

    def get(self, app):
        app_dir = self.CreateAppDir(app)
        reply = {}
        reply['minimum-required'] = self.GetAliasVersion(app, 'minimum-required')
        reply['latest-version'] = self.GetAliasVersion(app, 'latest-version')
        content = json.dumps(reply)
        self.write(content)

    def post(self, app):
        app_dir = self.CreateAppDir(app)
        body = json.loads(self.request.body)
        minimum_required = self.FindVersion(app, body['minimum-required'])
        try:
            with codecs.open(app_dir + '/alias.json', mode='r+', encoding='utf-8') as fp:
                alias_json = json.load(fp)
                alias_json['minimum-required'] = minimum_required
                fp.seek(0)
                json.dump(alias_json, fp)
        except (OSError, FileNotFoundError):
            self.send_error(400, **{'exc_info': sys.exc_info()})

class Alias(BaseHandler):

    def post(self, app):
        app_dir = self.CreateAppDir(app)
        body = json.loads(self.request.body)
        alias = body['alias']
        version = body['version']
        try:
            with codecs.open(app_dir + '/alias.json', mode='r+', encoding='utf-8') as fp:
                alias_json = json.load(fp)
                if not version.endswith('.json'):
                    version += '.json'
                alias_json[alias] = version
                fp.seek(0)
                json.dump(alias_json, fp)
        except (OSError, FileNotFoundError):
            print(sys.exc_info())
            self.send_error(400, **{'exc_info': sys.exc_info()})

class Merge(BaseHandler):

    def post(self, app):
        app_dir = self.CreateAppDir(app)
        body = json.loads(self.request.body)
        v1 = body['merge-to']
        v2 = body['merge-from']
        v1_json = None
        v2_json = None
        merge_success = True

        print(self.request.body)
        v1 = self.FindVersion(app, v1)
        v2 = self.FindVersion(app, v2)

        v1 = v1[:-5]
        v2 = v2[:-5]
        print(v2, v1)
        try:
            versions = os.listdir(app_dir)
            if 'alias.json' in versions:
                versions.remove('alias.json')

            def remove_postfix(v):
                return v[:-5]
            versions = map(remove_postfix, versions)

            def find(v):
                return v >= v2 and v <= v1
            versions = filter(find, versions)

            def merge(a, b):
                try:
                    with codecs.open(app_dir + '/' + b + '.json', mode='r', encoding='utf-8') as fp:
                        j = json.load(fp)
                        b = j['files']
                        a.extend(b)
                except (OSError, FileNotFoundError):
                    pass
                return a
            files = reduce(merge, versions, [])

        except (OSError, FileNotFoundError):
            pass

        new_version = {}
        ver = datetime.datetime.now()
        verstr = ver.isoformat()
        new_version['version'] = verstr
        new_version['files'] = files
        fname = verstr + '.json'
        try:
            with codecs.open(app_dir + '/' + fname, mode='w', encoding='utf-8') as fp:
                json.dump(new_version, fp)
            reply = {}
            reply['version'] = verstr + '.json'
            self.write(json.dumps(reply))
        except (OSError, FileNotFoundError):
            self.send_error(400, **{'exc_info': sys.exc_info()})
